
#include <vector>
#include <boost/lexical_cast.hpp>
#include <boost/filesystem.hpp>
#include <boost/scoped_ptr.hpp>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include "SyncFile.h"
#include "../client-common/DBHelper.h"
#include "../client-common/VersionTag.h"
#include "../client-common/Config.h"
#include "../client-common/TableSerialized.h"
#include "../port/System.h"
#include "../port/convert.h"

namespace Client {
namespace File {

using namespace Common;

typedef CTableCached<CSyncFile> CTableSyncFile;
typedef CTableSerialized<CSyncFile> CTableSyncFileSerialized;
static string TABLE_NAME = "file";

static std::vector<TableTriggerRef> mTriggers;

static void TriggerRecord(RecordRef r, int type) {
	for(std::vector<TableTriggerRef>::iterator i = mTriggers.begin(); i != mTriggers.end(); i++) {
		switch(type) {
		case 0 :
			(*i)->OnAdded(r);
			break;
		case 1 :
			(*i)->OnDeleted(r);
			break;
		case 2 :
			(*i)->OnUpdated(r);
			break;
		}
	}
}

void CSyncFile::AddTrigger(TableTriggerRef trig)
{
	mTriggers.push_back(trig);
}

CSyncFile::CSyncFile(CDBCursor& c) : CRecord(c)
{
	c.GetString(1, mId);
	c.GetString(2, mName);
	c.GetInt(3, mType);
	c.GetInt(4, mVersion);
	c.GetString(5, mOrigDevice);
	c.GetString(6, mStorePath);
	c.GetString(7, mOrigPath);
	c.GetString(8, mStoreTag);
	c.GetInt64(9, mSize);
	c.GetString(10, mParent);
	c.GetInt(11, mStatus);
	c.GetInt(12, mSyncCount);
	c.GetString(13, mLastModifyDevice);
}

CSyncFile::CSyncFile(string& file_path, CSyncFile* parent) :
	mStatus(FILE_STATUS_NORMAL), mSyncCount(0), mVersion(0)
{
	boost::filesystem::path file = file_path;
	mName = file.filename().string();
	mStorePath = file.string();
	mOrigPath = mStorePath;
	mStoreTag = boost::lexical_cast<string>(boost::filesystem::last_write_time(file));
	mSize = boost::filesystem::file_size(file);
	if(parent) mParent = parent->mId;
	mType = FILE_TYPE_FILE;
}

string& CSyncFile::GetTableName()
{
	return TABLE_NAME;
}

void CSyncFile::GetValueSet(map<string, string>& values)
{
	values["_id"] = GetValueDesc(mId);
	values["name"] = GetValueDesc(mName);
	values["type"] = GetValueDesc(mType);
	values["version"] = GetValueDesc(mVersion);
	values["device"] = GetValueDesc(mOrigDevice);
	values["store_path"] = GetValueDesc(mStorePath);
	if(mOrigPath.length() > 0) values["orig_path"] = GetValueDesc(mOrigPath);
	values["store_tag"] = GetValueDesc(mStoreTag);
	values["size"] = GetValueDesc(mSize);
	if(mParent.length() > 0) values["parent"] = GetValueDesc(mParent);
	values["status"] = GetValueDesc(mStatus);
	values["sync_count"] = GetValueDesc(mSyncCount);
	values["mod_device"] = GetValueDesc(mLastModifyDevice);
}

CSyncFile::CSyncFile(JsonValue& v) : mSyncCount(0)
{
	SetValue(v["id"], mId);
	SetValue(v["name"], mName);
	SetValue(v["type"], mType);
	SetValue(v["ver"], mVersion);
	SetValue(v["dev"], mOrigDevice);
	SetValue(v["orig_path"], mOrigPath);
	SetValue(v["size"], mSize);
	SetValue(v["parent"], mParent);
	SetValue(v["status"], mStatus);
	SetValue(v["mod_dev"], mLastModifyDevice);
}

void CSyncFile::WriteJson(JsonWriter& writer)
{	
	writer.StartObject();
	writer.String("id").String(mId.c_str());
	writer.String("name").String(mName.c_str());
	writer.String("type").Int(mType);
	writer.String("ver").Int(mVersion);
	writer.String("dev").String(mOrigDevice.c_str());
	writer.String("orig_path").String(mOrigPath.c_str());
	writer.String("size").Int64(mSize);
	if(mParent.length() > 0) writer.String("parent").String(mParent.c_str());
	writer.String("status").Int(mStatus);
	writer.String("mod_dev").String(mLastModifyDevice.c_str());
	writer.EndObject();

}

TableRef CSyncFile::Query()
{
	return CTableSyncFile::Query(TABLE_NAME, string(), string());
}

TableRef CSyncFile::QueryAfter(int ver)
{
	CConfig& conf = CConfig::GetInstance();
	string s1;
	string s2;
	return CTableSyncFile::Query(
			TABLE_NAME, 
			"version>" + GetValueDesc(ver, s1) + " and mod_device=" + GetValueDesc(conf.mDeviceId, s2), 
			string()
			);
}

SyncFileRef CSyncFile::QueryById(string& id)
{
	string s;
	TableRef t = CTableSyncFile::Query(TABLE_NAME, "_id=" + GetValueDesc(id, s), string());
	if(!t || t->GetCount() == 0) return SyncFileRef();

	return boost::dynamic_pointer_cast<CSyncFile, CRecord>(t->NextRecords(1)[0]);
}

class CActionDeleteFile : public CVersionTag::CCommitAction {
	
	CSyncFile& mFile;

public :

	CActionDeleteFile(CSyncFile& sf) : mFile(sf) {  };
	virtual bool doAction(int new_ver) {
		mFile.mStatus = FILE_STATUS_DELETED;
		mFile.mVersion = new_ver;
		mFile.mLastModifyDevice = CConfig::GetInstance().mDeviceId;
		return CDBHelper::GetInstance().Update(mFile);
	}
};

void CSyncFile::DelFile(CSyncFile& sf)
{
	boost::scoped_ptr<CActionDeleteFile> action(new CActionDeleteFile(sf));
	if(CVersionTag::UpdateVersion(string(VERSION_TAG_FILE), *action)) {
		TriggerRecord(sf.shared_from_this(), 1);
	}
}

class CActionAddFile : public CVersionTag::CCommitAction {
	
	CSyncFile& mFile;

public :

	CActionAddFile(CSyncFile& sf) : mFile(sf) {  };
	virtual bool doAction(int new_ver) {
		mFile.mVersion = new_ver;
		if(mFile.mIndex < 0) {
			return CDBHelper::GetInstance().Insert(mFile);
		}
		else {
			return CDBHelper::GetInstance().Update(mFile);
		}
	}
};

SyncFileRef CSyncFile::AddLocalFile(string& file_path, CSyncFile* parent)
{
	// can not add file before registered
	CConfig conf = CConfig::GetInstance();
	if(conf.mDeviceId.length() == 0) return SyncFileRef();

	string s;
	TableRef tf = CTableSyncFile::Query(TABLE_NAME, "store_path=" + GetValueDesc(file_path, s), string());

	SyncFileRef oldsf;
	if(tf) {
		oldsf = boost::dynamic_pointer_cast<CSyncFile, CRecord>(tf->NextRecords(1)[0]);
		tf->Close();
	}
	
	if(oldsf && oldsf->mStatus != FILE_STATUS_DELETED) {
		oldsf.reset();
		return oldsf;
	}

	SyncFileRef sf;
	if(oldsf) {
		sf = oldsf;
		sf->mStatus = FILE_STATUS_NORMAL;
	}
	else {
		sf = SyncFileRef(new CSyncFile(file_path, parent));
	}
		
	sf->mOrigDevice = conf.mDeviceId;
	sf->mLastModifyDevice = conf.mDeviceId;

	sf->mId = conf.mDeviceId + boost::lexical_cast<string>(Util::CSystem::GetCurrentSeconds());

	boost::scoped_ptr<CActionAddFile> action(new CActionAddFile(*sf));
	if(!CVersionTag::UpdateVersion(string(VERSION_TAG_FILE), *action)) {
		sf.reset();
	}
	else {
		TriggerRecord(sf, 0);
	}

	return sf;
}

bool CSyncFile::AddSyncFile(SyncFileRef sf)
{
	Engine::DebugStream << "CSyncFile::AddSyncFile - " << sf->mId << ", status : " << sf->mStatus << '\n';
	SyncFileRef oldsf = QueryById(sf->mId);
	if(!oldsf) {
		if(sf->mStatus != FILE_STATUS_DELETED) {
			if(!CDBHelper::GetInstance().Insert(*sf)) return false;
			TriggerRecord(sf, 0);
		}
	}
	else {
		if(sf->mStatus == FILE_STATUS_DELETED) {
			string filename;
			Util::CConverter::UTF8_TO_Ascii(oldsf->mStorePath, filename);
			Engine::DebugStream << "CSyncFile::AddSyncFile - delete file : " << filename << '\n';
			boost::filesystem::path file = filename;
			if(boost::filesystem::exists(file)) boost::filesystem::remove(file);
			if(!CDBHelper::GetInstance().Delete(*oldsf)) return false;
			TriggerRecord(sf, 1);
		}
		else {
			sf->mIndex = oldsf->mIndex;
			if(!CDBHelper::GetInstance().Update(*sf)) return false;
			TriggerRecord(sf, 2);
		}
	}

	return true;
}

bool CSyncFile::UpdateSyncFile(SyncFileRef sf)
{
	return CDBHelper::GetInstance().Update(*sf);
}

TableRef CSyncFile::GetSerializedTable(string& js)
{
	return TableRef(new CTableSyncFileSerialized(js));
}

TableRef CSyncFile::GetSerializedTable(CTable& table)
{
	return CTableSyncFileSerialized::Create(table);
}

bool CSyncFile::LessVersion(const SyncFileRef& sf1, const SyncFileRef& sf2) {
	return sf1->mVersion < sf2->mVersion;
}

} // Client
} // File